import datetime
import threading

# TODO: Check attributes before setting


class Person(object):
    @property
    def firstName(self):
        return self._firstName

    @firstName.setter
    def firstName(self, firstName):
        self._firstName = firstName

    @property
    def lastName(self):
        return self._lastName

    @lastName.setter
    def lastName(self, lastName):
        self._lastName = lastName

    @property
    def sex(self):
        return self._sex

    @sex.setter
    def sex(self, sex):
        self._sex = sex

    @property
    def birthday(self):
        return self._birthday

    @birthday.setter
    def birthday(self, birthday):
        self._birthday = birthday

    @property
    def address(self):
        return self._address

    @address.setter
    def address(self, address):
        self._address = address

    @property
    def phoneNumber(self):
        return self._phoneNumber

    @phoneNumber.setter
    def phoneNumber(self, phoneNumber):
        self._phoneNumber = phoneNumber


class Book(object):
    @property
    def ISBN(self):
        return self._ISBN

    @ISBN.setter
    def ISBN(self, ISBN):
        self._ISBN = ISBN

    @property
    def title(self):
        return self._title

    @title.setter
    def title(self, title):
        self._title = title

    @property
    def catagory(self):
        return self._catagory

    @catagory.setter
    def catagory(self, catagory):
        self._catagory = catagory

    @property
    def author(self):
        return self._author

    @author.setter
    def author(self, author):
        self._author = author

    @property
    def publisher(self):
        return self._publisher

    @publisher.setter
    def publisher(self, publisher):
        self._publisher = publisher

    @property
    def publicationDate(self):
        return self._publicationDate

    @publicationDate.setter
    def publicationDate(self, publicationDate):
        self._publicationDate = publicationDate

    @property
    def pages(self):
        return self._pages

    @pages.setter
    def pages(self, pages):
        self._pages = pages

    @property
    def price(self, price):
        return self._price

    @price.setter
    def price(self, price):
        self._price = price


class BookItem(Book):
    @property
    def barCode(self):
        return self._barCode

    @barCode.setter
    def barCode(self, barCode):
        self._barCode = barCode

    @property
    def isBorrowed(self):
        return self._isBorrowed

    @isBorrowed.setter
    def isBorrowed(self, isBorrowed):
        self._isBorrowed = isBorrowed


class Borrower(Person):
    @property
    def card(self):
        return self._card

    @card.setter
    def card(self, card):
        self._card = card

    @property
    def maxIssuedBooks(self):
        return self._maxIssuedBooks

    @maxIssuedBooks.setter
    def maxIssuedBooks(self, maxIssuedBooks):
        self._maxIssuedBooks = maxIssuedBooks

    @property
    def issuedBooks(self):
        return self._issuedBooks

    @issuedBooks.setter
    def issuedBooks(self, issuedBooks):
        self._issuedBooks = issuedBooks

    @property
    def dailyFee(self):
        return self._dailyFee

    @dailyFee.setter
    def dailyFee(self, dailyFee):
        self._dailyFee = dailyFee

    def payBill(self, bill):
        return self._card.deduct(bill)


class Card(object):
    @property
    def id(self):
        return self._id

    @id.setter
    def id(self, id):
        self._id = id

    @property
    def holder(self):
        return self._holder

    @holder.setter
    def holder(self, holder):
        self._holder = holder

    @property
    def issuedTime(self):
        return self._issuedTime

    @issuedTime.setter
    def issuedTime(self, issuedTime):
        self._issuedTime = issuedTime

    @property
    def expiredTime(self):
        return self._expiredTime

    @expiredTime.setter
    def expiredTime(self, expiredTime):
        self._expiredTime = expiredTime

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, balance):
        self._balance = balance

    def deduct(self, amount):
        if self._balance >= amount:
            self._balance -= amount
            return self._balance
        else:
            pass
            # TODO: Not enough balance!

    def recharge(self, amount):
        self._balance += amount
        return self._balance

    def queryBalance(self):
        return self._balance


class Issue(object):
    @property
    def borrower(self):
        return self._borrower

    @borrower.setter
    def borrower(self, borrower):
        self._borrower = borrower

    @property
    def manager(self):
        return self._manager

    @manager.setter
    def manager(self, manager):
        self._manager = manager

    @property
    def bookItem(self):
        return self._bookItem

    @bookItem.setter
    def bookItem(self, bookItem):
        self._bookItem = bookItem

    @property
    def issuedDate(self):
        return self._issuedDate

    @issuedDate.setter
    def issuedDate(self, issuedDate):
        self._issuedDate = issuedDate

    @property
    def returnedDate(self):
        return self._returnedDate

    @returnedDate.setter
    def returnedDate(self, returnedDate):
        self._returnedDate = returnedDate

    @property
    def bill(self):
        return self._bill

    @bill.setter
    def bill(self, bill):
        self._bill = bill

    def calBill(self):
        days = (self._returnedDate - self._issuedDate).days
        return self._borrower.dailyFee * days


class Library(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        if not hasattr(Library, "_instance"):
            with Library._instance_lock:
                if not hasattr(Library, "_instance"):
                    Library._instance = object.__new__(cls)
        return Library._instance

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

    @property
    def location(self):
        return self._location

    @location.setter
    def location(self, location):
        self._location = location

    @property
    def openTime(self):
        return self._openTime

    @openTime.setter
    def openTime(self, openTime):
        self._openTime = openTime

    @property
    def closeTime(self):
        return self._closeTime

    @closeTime.setter
    def closeTime(self, closeTime):
        self._closeTime = closeTime

    @property
    def totalSits(self):
        return self._totalSits

    @totalSits.setter
    def totalSits(self, totalSits):
        self._totalSits = totalSits
        self.availableSits = totalSits

    @property
    def availableSits(self):
        return self._availableSits

    @availableSits.setter
    def availableSits(self, availableSits):
        self._availableSits = availableSits

    @property
    def issues(self):
        return self._issues

    @issues.setter
    def issues(self, issues):
        self._issues = issues

    @property
    def managers(self):
        return self._managers

    @managers.setter
    def managers(self, managers):
        self._managers = managers

    @property
    def borrowers(self):
        return self._borrowers

    @borrowers.setter
    def borrowers(self, borrowers):
        self._borrowers = borrowers

    @property
    def bookItems(self):
        return self._bookItems

    @bookItems.setter
    def bookItems(self, bookItems):
        self._bookItems = bookItems

    def isOpen(self):
        now = datetime.datetime.now().time()
        if (now > self.openTime) and (now < self.closeTime):
            return True

        return False

    def occupySit(self):
        if self.availableSits <= 0:
            pass  # TODO: Handle this
        else:
            self.availableSits -= 1

        return self.availableSits

    def releaseSit(self):
        if self.availableSits < self.totalSits:
            self.availableSits += 1

    def getTotalBooks(self):
        return len(self._bookItems)

    def searchBooks(self, barCode=None, ISBN=None):
        bookItems = []
        for book in self.bookItems:
            if (barCode != None) and (barCode == book.barCode):
                bookItems.append(book)
                break

            if (ISBN != None) and (ISBN == book.ISBN):
                bookItems.append(book)
                break
        return bookItems

    def addBookItem(self, bookItem):
        for b in self.bookItems:
            if bookItem.barCode == b.barCode:
                return

        self.bookItems.append(bookItem)

    def removeBookItem(self, bookItem):
        self.bookItems.remove(bookItem)

    def searchBorrower(self, cardID):
        for borrower in self.borrowers:
            if cardID == borrower.card.id:
                return borrower

        return None

    def addBorrower(self, borrower):
        if not borrower in self.borrowers:
            self.borrowers.append(borrower)

    def removeBorrower(self, borrower):
        self.borrowers.remove(borrower)

    def addManager(self, manager):
        if not manager in self.managers:
            self.managers.append(manager)

    def removeManager(self, manager):
        self.managers.remove(manager)


class Manager(Person):
    @property
    def salary(self):
        return self._salary

    @salary.setter
    def salary(self, salary):
        self._salary = salary

    @property
    def onDuty(self):
        return self._onDuty

    @onDuty.setter
    def onDuty(self, onDuty):
        self._onDuty = onDuty

    def verifyCardInfo(self, card):
        # TODO: Verified by card reader
        return True

    def issueBook(self, borrower, bookItem):
        library = Library()
        if not self.verifyCardInfo(borrower.card.id):
            pass  # TODO: Fix this
        else:
            if len(borrower.issuedBooks) < borrower.maxIssuedBooks:
                issue = Issue()

                bookItem.isBorrowed = True
                borrower.issuedBooks.append(bookItem)

                issue.borrower = borrower
                issue.bookItem = bookItem
                issue.manager = self
                issue.issuedDate = datetime.datetime.today()
                library.issues.append(issue)
            else:
                pass  # TODO: Borrow too manay books

    def receiveBook(self, bookItem):
        library = Library()
        issue = None
        for i in library.issues:
            if i.bookItem.barCode == bookItem.barCode:
                issue = i
                break

        if issue != None:
            issue.returnedDate = datetime.datetime.today()
            issue.borrower.payBill(issue.calBill())
            issue.borrower.issuedBooks.remove(bookItem)
            issue.bookItem.isBorrowed = False
